package gbench.common.matlib.img;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.function.Consumer;

import gbench.common.matlib.MatlibCanvas.NVec;
import gbench.common.matlib.MatlibCanvas.Range;
import gbench.common.matlib.data.DataReader;

import static gbench.common.matlib.img.ImageOps.rgb2clr;

/**
 * 图片BufferedImage的包裹类<br>
 * 提供一些便捷的绘图控制操作：主要用于绘制坐标轴
 *
 * @author gbench
 */
public class ImageWrapper {
    
    /**
     * 包装一个BufferedImage ( 对 纵向坐标轴 进行翻转 )
     *
     * @param img     图片对象
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper(final BufferedImage img) {
        this(img,true);
    }
    
    /**
     * 包装一个BufferedImage
     *
     * @param img     图片对象
     * @param reverse 是否对纵向坐标进行翻转,<br>
     *                由于图片的纵轴是从下向下递增，而与常用数学顺序，从下向上递增相反，<br>
     *                故一般设置为true，表示进行纵向坐标(y)翻转，以绘制出复合数学习惯的图表。<br>
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper(final BufferedImage img, boolean reverse) {
        this.graphics = img.getGraphics();
        this.height = img.getHeight();
        this.width = img.getWidth();
        this.reverse = reverse;
    }

    /**
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param x1 the first point's x coordinate.
     * @param y1 the first point's y coordinate.
     * @param x2 the second point's x coordinate
     * @param y2 the second point's y coordinate
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawLine(Number x1, Number y1, Number x2, Number y2) {
        graphics.drawLine(x1.intValue(), reverse ? height - y1.intValue() : y1.intValue(), x2.intValue(),
                reverse ? height - y2.intValue() : y2.intValue());
        return this;
    }

    /**
     * 水平的线<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param xrng x的轴向范围
     * @param y    的高度
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawHorizontalLine(final Range xrng, Number y) {
        this.drawLine(xrng.start(), y, xrng.end(), y);
        return this;
    }

    /**
     * 水平的线:drawHorizontalLine 别名<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param xrng x的轴向范围
     * @param y    的高度
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper HL(final Range xrng, Number y) {
        this.drawHorizontalLine(xrng, y);
        return this;
    }

    /**
     * 水平的线<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param xrng  x的轴向范围
     * @param y     的高度
     * @param color 线条颜色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawHorizontalLine(final Range xrng, Number y, final String color) {
        final var old_clr = this.graphics.getColor();
        this.setColor(color);
        this.drawLine(xrng.start(), y, xrng.end(), y);
        this.graphics.setColor(old_clr);
        return this;
    }

    /**
     * 水平的线:drawHorizontalLine 别名<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param xrng  x的轴向范围
     * @param y     的高度
     * @param color 线条颜色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper HL(final Range xrng, Number y, final String color) {
        return this.drawHorizontalLine(xrng, y, color);
    }

    /**
     * 垂直的线<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param yrng y的轴向范围
     * @param x    的偏移
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawVerticalLine(final Range yrng, Number x) {
        this.drawLine(x, yrng.start(), x, yrng.end());
        return this;
    }

    /**
     * 垂直的线<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param yrng y的轴向范围
     * @param x    的偏移
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper VL(final Range yrng, Number x) {
        return this.drawVerticalLine(yrng, x);
    }

    /**
     * 垂直的线<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param yrng  y的轴向范围
     * @param x     的偏移
     * @param color 线条颜色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawVerticalLine(final Range yrng, Number x, final String color) {
        final var old_clr = this.graphics.getColor();
        this.setColor(color);
        this.drawLine(x, yrng.start(), x, yrng.end());
        this.graphics.setColor(old_clr);
        return this;
    }

    /**
     * 垂直的线:drawVerticalLine 别名<br>
     * Draws a line, using the current color, between the points (x1, y1) and (x2,y2) <br>
     * in this graphics context's coordinate system.
     *
     * @param yrng  y的轴向范围
     * @param x     的偏移
     * @param color 线条颜色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper VL(final Range yrng, Number x, final String color) {
        return this.drawVerticalLine(yrng, x, color);
    }

    /**
     * Draws the text given by the specified string, using thisgraphics context's
     * current font and color. <br>
     * The baseline of theleftmost character is at position (x, y) in this graphics
     * context's coordinate system.<br>
     *
     * @param str the string to be drawn.
     * @param x   the x coordinate
     * @param y   the y coordinate.
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawString(Object str, Number x, Number y) {
        return this.drawString(str, x, y, (NVec) null);
    }

    /**
     * Draws the text given by the specified string, using thisgraphics context's
     * current font and color. <br>
     * The baseline of theleftmost character is at position (x, y) in this graphics
     * context's coordinate system.<br>
     *
     * @param str    the string to be drawn.
     * @param x      the x coordinate
     * @param y      the y coordinate.
     * @param affine x,y的仿射变换 [scalex x轴放大倍率,scaley y轴放大倍率,offsetx x水平方向偏移,offsety y垂直方向偏移]
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawString(final Object str, final Number x, final Number y, final NVec affine) {
        if (affine == null || affine.size() < 4) {
            graphics.drawString(str + "", x.intValue(), reverse ? height - y.intValue() : y.intValue());
        } else {
            final var scalex = affine.get(0); // 水平放大倍率
            final var scaley = affine.get(1); // 垂直放大倍率
            final var offsetx = affine.get(2); // 水平平移位置
            final var offsety = affine.get(3); // 垂直偏移位置
            final var _x = x.doubleValue(); // x 坐标的浮点值
            final var _y = y.doubleValue(); // x 坐标的浮点值
            final var __x = (int) (_x * scalex + offsetx); // 对 x 坐标 做仿射变换
            final var __y = reverse
                    ? (height - (int) (_y * scaley + offsety))
                    : (int) (_y * scaley + offsety); // 对 y 坐标 做仿射变换
            graphics.drawString(str + "", __x, __y); // 绘制字符串
        }
        return this;
    }

    /**
     * Draws the text given by the specified string, using thisgraphics context's
     * current font and color. <br>
     * The baseline of theleftmost character is at position (x, y) in this graphics
     * context's coordinate system.<br>
     *
     * @param str   the string to be drawn.
     * @param x     the x coordinate
     * @param y     the y coordinate.
     * @param color 文字颜色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawString(final Object str, final Number x, final Number y, final String color) {
        return this.drawString(str, x, y, color, null);
    }

    /**
     * Draws the text given by the specified string, using thisgraphics context's
     * current font and color. <br>
     * The baseline of theleftmost character is at position (x, y) in this graphics
     * context's coordinate system.<br>
     *
     * @param str    the string to be drawn.
     * @param x      the x coordinate
     * @param y      the y coordinate.
     * @param color  文字颜色
     * @param affine x,y的仿射变换 [scalex,scaley,offsetx,offsety]
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper drawString(final Object str, final Number x, final Number y, final String color, final NVec affine) {
        final var old_clr = this.graphics.getColor();
        this.setColor(color);
        this.drawString(str, x, y, affine);
        this.graphics.setColor(old_clr);
        return this;
    }
    
    /**
     * 提取字体
     * 
     * @param fontFile   a File object representing theinput data for the font.
     * @param style 字体式样
     * @param size 字体大小
     * @return Font 字体对象 ( 参数 非法 返回 null )
     */
    public Font getFont(final String fontFile,final int style,final float size) {
        return this.getFont(Font.TRUETYPE_FONT, fontFile).deriveFont(style,size);
    }
    
    /**
     * 提取字体
     * 
     * @param fontStream an InputStream object representing the input data for the font.
     * @param style 字体式样
     * @param size 字体大小
     * @return Font 字体对象 ( 参数 非法 返回 null )
     */
    public Font getFont(final InputStream fontStream,final int style,final float size) {
        return this.getFont(Font.TRUETYPE_FONT, fontStream).deriveFont(style,size);
    }
    
    /**
     * 提取字体
     * 
     * @param fontFormat the type of the Font, which is TRUETYPE_FONT if a TrueType
     *                   resource is specified.or TYPE1_FONT if a Type 1 resource is specified.
     * @param fontStream an InputStream object representing theinput data for the font.
     * @return Font 字体对象 ( 参数 非法 返回 null )
     */
    public Font getFont(final int fontFormat, final InputStream fontStream) {
        Font font = null;
        try {
            font = Font.createFont(fontFormat, fontStream);
        } catch (FontFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return font;
    }
    
    /**
     * 提取字体
     * 
     * @param fontFormat the type of the Font, which is TRUETYPE_FONT if a TrueType resource is <br>
     *                   specified.or TYPE1_FONT if a Type 1 resource is specified.
     * @param fontFile   a File object representing theinput data for the font.
     * @return Font 字体对象 ( 参数 非法 返回 null )
     */
    public Font getFont(final int fontFormat, final String fontFile) {
        Font font = null;
        try {
            font = Font.createFont(fontFormat, new File(fontFile));
        } catch (FontFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return font;
    }
    
    /**
     * 提取字体
     * 
     * @param fontFormat the type of the Font, which is TRUETYPE_FONT if a TrueType resource is <br>
     *                   specified.or TYPE1_FONT if a Type 1 resource is specified.
     * @param fontFile   a File object representing theinput data for the font.
     * @return Font 字体对象 ( 参数 非法 返回 null )
     */
    public Font getFont(final int fontFormat, final File fontFile) {
        Font font = null;
        try {
            font = Font.createFont(fontFormat, fontFile);
        } catch (FontFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return font;
    }
    
    /**
     * 创建字体
     * 
     * @param fontFormat the type of the Font, which is TRUETYPE_FONT if a TrueType resource is <br>
     *                   specified.or TYPE1_FONT if a Type 1 resource is specified.
     * @param fontFile   a File object representing the input data for the font.
     * @return Font 字体对象
     * @throws IOException
     * @throws FontFormatException
     */
    public Font createFont(final int fontFormat, final File fontFile) 
        throws FontFormatException, IOException {
        return Font.createFont(fontFormat, fontFile);
    }
    
    /**
     * 创建字体
     * 
     * @param fontFormat the type of the Font, which is TRUETYPE_FONT if a TrueType
     *                   resource is specified.or TYPE1_FONT if a Type 1 resource is specified.<br>
     * @param fontStream an InputStream object representing the input data for the font.
     * @return Font 字体对象
     * @throws IOException
     * @throws FontFormatException
     */
    public Font createFont(final int fontFormat, final InputStream fontStream) 
        throws FontFormatException, IOException {
        return Font.createFont(fontFormat, fontStream);
    }
    
    /**
     * Sets this graphics context's font to the specified font.All subsequent text <br>
     * operations using this graphics context use this font. A null argument is silently <br>
     * ignored.<br>
     * @param font the font. 字体
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper setFont(final Font font) {
        this.graphics.setFont(font);
        return this;
    }

    /**
     * 生成一个绘图函数
     *
     * @param x the x coordinate
     * @param y the y coordinate.
     * @return str->drawString(str,x,y)
     */
    public Consumer<Object> drawStringF(Number x, Number y) {
        return str -> this.drawString(str, x, y);
    }

    /**
     * Gets this graphics context's current color.
     *
     * @return this graphics context's current color.
     */
    public Color getColor() {
        return this.graphics.getColor();
    }

    /**
     * Sets this graphics context's current color to the specifiedcolor. <br>
     * All subsequent graphics operations using this graphicscontext use this specified color.
     *
     * @param c the new rendering color.
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper setColor(Color c) {
        graphics.setColor(c);
        return this;
    }

    /**
     * Sets this graphics context's current color to the specifiedcolor. <br>
     * All subsequent graphics operations using this graphicscontext use this specified color.
     *
     * @param color the new rendering color. 例如"#ff0000"表示红色
     * @return ImageWrapper 自身以便实现链式编程
     */
    public ImageWrapper setColor(String color) {
        graphics.setColor(rgb2clr(color));
        return this;
    }

    /**
     * 图片高度
     *
     * @return 图片高度
     */
    public int height() {
        return this.height;
    }

    /**
     * 图片宽度
     *
     * @return 图片宽度
     */
    public int width() {
        return this.width;
    }

    /**
     * 高度是否被翻转
     *
     * @return 高度是否被翻转
     */
    public boolean reverse() {
        return this.reverse;
    }

    /**
     * 获取图形对象
     *
     * @return 图形对象
     */
    public Graphics graphics() {
        return this.graphics;
    }
    
    /**
     * 获取图形对象2D
     *
     * @return 图形对象2D
     */
    public Graphics2D graphics2D() {
        return (Graphics2D)this.graphics;
    }
    
    /**
     * Sets the value of a single preference for the rendering algorithms. <br>
     * Hint categories include controls for rendering quality and overalltime/quality <br>
     * trade-off in the rendering process. Refer to the RenderingHints class for definitions <br>
     * of some commonkeys and values. <br>
     * 
     * @param hintKey the key of the hint to be set.
     * @param hintValue the value indicating preferences for the specifiedhint category.
     * @return ImageWrapper 对象本身
     */
    public ImageWrapper setRenderingHint(final Key hintKey, final Object hintValue) {
        graphics2D().setRenderingHint(hintKey, hintValue);
        return this;
    }
    
    /**
     * 开启 抗锯齿 
     * @return ImageWrapper
     */
    public ImageWrapper antiAliasing() {
        graphics2D().setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
           RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
        return this;
    }

    /**
     * 坐标系统
     */
    public class CoordinateSys {

        /**
         * @param x_rng        x 的绘图区域范围
         * @param y_rng        y 的绘图区域范围
         * @param dtx          数据空间的 水平采样点
         * @param dty          数据空间的 垂直采样点
         * @param xs           转换成绘图水平坐标的点 :x values
         * @param ys           转换成绘图垂直坐标的点 :y values
         * @param axisColor    坐标轴颜色
         * @param tickerColor  标度的颜色
         * @param margin       边际余量单位,用于绘制坐标轴的延展方向。
         * @param ytick_offset y轴刻度边际保留
         * @param xtick_offset x轴刻度边际保留
         */
        public CoordinateSys(final Range x_rng, final Range y_rng,
                             final NVec dtx, final NVec dty,
                             final NVec xs, final NVec ys,
                             final Color axisColor, final Color tickerColor,
                             final int margin, final int ytick_offset, final int xtick_offset) {
            this.x_rng = x_rng;
            this.y_rng = y_rng;
            this.dtx = dtx;
            this.dty = dty;
            this.xs = xs;
            this.ys = ys;
            this.axisColor = axisColor;
            this.tickerColor = tickerColor;
            this.margin = margin;
            this.ytick_offset = ytick_offset;
            this.xtick_offset = xtick_offset;
        }// CoordinateSys

        /**
         * @param x_rng x 的绘图区域范围
         * @param y_rng y 的绘图区域范围
         * @param dtx   数据空间的 水平采样点
         * @param dty   数据空间的 垂直采样点
         * @param xs    转换成绘图水平坐标的点 :x values
         * @param ys    转换成绘图垂直坐标的点 :y values
         */
        public CoordinateSys(final Range x_rng, final Range y_rng,
                             final NVec dtx, final NVec dty,
                             final NVec xs, final NVec ys) {
            final var margin = 50; // 边际余量单位,用于绘制坐标轴的延展方向。
            final var ytick_offset = 30; // y轴刻度边际保留
            final var xtick_offset = -13; // x轴刻度边际保留
            this.x_rng = x_rng;
            this.y_rng = y_rng;
            this.dtx = dtx;
            this.dty = dty;
            this.xs = xs;
            this.ys = ys;
            this.axisColor = rgb2clr("#fffff");
            this.tickerColor = rgb2clr("#ff0000");
            this.margin = margin;
            this.ytick_offset = ytick_offset;
            this.xtick_offset = xtick_offset;
        }// CoordinateSys

        /**
         * 绘制坐标轴
         *
         * @return ImageWrapper 图绘制对象
         */
        public ImageWrapper render() {
            final var g = ImageWrapper.this; // 绘图器
            final var nf = NumberFormat.getInstance(); // 数据格式化器
            // x水平轴
            g.setColor(this.axisColor);
            g.drawHorizontalLine(x_rng.offset(-margin, margin), y_rng.start()); // X水平轴
            xs.foreach((i, x) -> g.drawString(nf.format(dtx.get(i)), x, y_rng.start() + xtick_offset)); // 水平轴上的 业务数据值

            // 水平轴的刻度-标准
            g.setColor(this.tickerColor);
            final var xdtf = DataReader.detransformer(x_rng, dtx.rangeL()); // 绘图空间到数据空间的变换 X DeTransFormer
            x_rng.offsetEnd(3 * margin).limit(6).forEach(x -> g.drawString(nf.format(xdtf.apply(x)), x, y_rng.start() + 3 * xtick_offset));

            // y垂直轴
            g.setColor(this.axisColor);
            g.drawVerticalLine(y_rng.offset(-margin, margin), x_rng.getStart()); // 垂直轴
            ys.foreach((i, y) -> g.drawString(nf.format(dty.get(i)), x_rng.start() - ytick_offset, y)); // 垂直轴上的 业务数据值

            // 垂直轴的标准刻度
            g.setColor(this.tickerColor);
            final var ydtf = DataReader.detransformer(y_rng, dty.rangeL()); // 绘图空间到数据空间的变换 Y DeTransFormer
            y_rng.offsetEnd(margin).limit(10).forEach(y -> g.drawString(nf.format(ydtf.apply(y)), x_rng.start() - 3 * ytick_offset, y));
            return g;
        }

        public Range getX_rng() {
            return x_rng;
        }

        public Range getY_rng() {
            return y_rng;
        }

        public NVec getDtx() {
            return dtx;
        }

        public NVec getDty() {
            return dty;
        }

        public NVec getXs() {
            return xs;
        }

        public NVec getYs() {
            return ys;
        }

        public Color getAxisColor() {
            return axisColor;
        }

        public Color getTickerColor() {
            return tickerColor;
        }

        public int getMargin() {
            return margin;
        }

        public int getYtick_offset() {
            return ytick_offset;
        }

        public int getXtick_offset() {
            return xtick_offset;
        }

        private final Range x_rng;
        private final Range y_rng;
        private final NVec dtx;
        private final NVec dty;
        private final NVec xs;
        private final NVec ys;
        private final Color axisColor;
        private final Color tickerColor;
        private final int margin;
        private final int ytick_offset;
        private final int xtick_offset;
    }

    /**
     * 绘制坐标轴
     *
     * @param x_rng        x 的绘图区域范围
     * @param y_rng        y 的绘图区域范围
     * @param dtx          数据空间的 水平采样点 (DaTa X)
     * @param dty          数据空间的 垂直采样点 (DaTa Y)
     * @param xs           转换成绘图水平坐标的点 :X valueS
     * @param ys           转换成绘图垂直坐标的点 :Y valueS
     * @param axisColor    坐标轴颜色
     * @param tickerColor  标度的颜色
     * @param margin       边际余量单位,用于绘制坐标轴的延展方向。
     * @param ytick_offset y轴刻度边际保留
     * @param xtick_offset x轴刻度边际保留
     */
    public ImageWrapper drawCoordinateSys(final Range x_rng, final Range y_rng,
                                          final NVec dtx, final NVec dty, final NVec xs, final NVec ys,
                                          final Color axisColor, final Color tickerColor,
                                          final int margin, final int ytick_offset, final int xtick_offset) {

        return new CoordinateSys(x_rng, y_rng, dtx, dty, xs, ys, axisColor, tickerColor, margin, ytick_offset, xtick_offset).render(); // 包装图片类
    }

    /**
     * 绘制坐标轴
     *
     * @param x_rng x 的绘图区域范围
     * @param y_rng y 的绘图区域范围
     * @param dtx   数据空间的 水平采样点 (DaTa X)
     * @param dty   数据空间的 垂直采样点 (DaTa Y)
     * @param xs    转换成绘图水平坐标的点 :X valueS
     * @param ys    转换成绘图垂直坐标的点 :Y valueS
     */
    public ImageWrapper drawCoordinateSys(final Range x_rng, final Range y_rng,
                                          final NVec dtx, final NVec dty, final NVec xs, final NVec ys) {

        return new CoordinateSys(x_rng, y_rng, dtx, dty, xs, ys).render(); // 包装图片类
    }

    private final int height;// 图片高度
    private final int width;// 图片高度
    private final Graphics graphics; // 绘图对象
    private final boolean reverse; // 是对高度进行翻转
}